package io.github.makbn.mcp.mediator.docker.internal;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.*;
import io.github.makbn.mcp.mediator.api.McpMediatorException;
import io.github.makbn.mcp.mediator.api.McpService;
import io.github.makbn.mcp.mediator.api.McpTool;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.apache.commons.io.FileUtils;
import org.jspecify.annotations.Nullable;

import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

/**
 * Docker Client Service for MCP Server.
 * All these annotations are generated by an LLM automatically.
 *
 * @author Matt Akbarian
 */
@McpService(
        name = "docker_mcp_server",
        description = "provides common docker command as mcp tools"
)
@RequiredArgsConstructor
@SuppressWarnings("unused")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class DockerClientService {

    DockerClient internalClient;

    @McpTool(name = "docker_start_container",
            description = "start an existing docker container using its containerId! containerId is required and " +
                    "can't be null or empty! if successful, returns true.")
    public boolean startContainerCmd(@NonNull String containerId) {
        internalClient.startContainerCmd(containerId)
                .exec();
        return true;
    }

    @McpTool(name = "docker_stop_container",
            description = "stop an existing docker container using its containerId! containerId is required and " +
                    "can't be null or empty! if successful, returns true.")
    public boolean stopContainerCmd(String containerId) {
        internalClient.stopContainerCmd(containerId)
                .exec();
        return true;
    }

    @McpTool(name = "docker_leave_swarm",
            description = "The docker swarm leave command is used to remove a node from a Docker Swarm. " +
                    "When executed on a node, it disconnects the node from the swarm, and it will no longer receive " +
                    "tasks from the swarm manager. no args is needed for this command")
    public boolean leaveSwarmCmd(boolean force) {
        LeaveSwarmCmd cmd = internalClient.leaveSwarmCmd();
        if (force) {
            cmd.withForceEnabled(true);
        }
        cmd.exec();

        return true;
    }

    @McpTool(name = "docker_container_diff",
            description = "List changes made to a container's filesystem since creation. " +
                    "Shows added (A), deleted (D), and changed (C) files. containerId is required and can't be null " +
                    "or empty")
    public List<ChangeLog> containerDiffCmd(String containerId) {
        return internalClient.containerDiffCmd(containerId)
                .exec();
    }

    @McpTool(name = "docker_build_image_file",
            description = "Build a Docker image from a Dockerfile or directory and returns the created imageId or" +
                    "error message in case of any exception. dockerFileOrFolderPath is the absolute path to the" +
                    "Dockerfile or the base directory that contains Dockerfile. baseDirectoryPath is the path to" +
                    "execute build and usually is the same as dockerFileOrFolderPath if not explicitly specified." +
                    "tags is a set of unique tags for the image name and tag. at least one tag should be in the tags." +
                    "build args are a list of key value pairs to be passed to the build. platform sets the target" +
                    "platform for the build.")
    public String buildImageCmd(String dockerFileOrFolderPath, String baseDirectoryPath, Set<String> tags, Map<String, String> buildArgs, String platform) throws ExecutionException, InterruptedException, TimeoutException {
        BuildImageCmd cmd = internalClient.buildImageCmd(new File(dockerFileOrFolderPath))
                .withBaseDirectory(new File(baseDirectoryPath));
        cmd.withTags(tags);

        if (buildArgs != null) {
            buildArgs.forEach(cmd::withBuildArg);
        }

        if (platform != null) {
            cmd.withPlatform(platform);
        }

        return Execute.getResult(cmd, BuildResponseItem::getImageId, ((step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT && t instanceof BuildResponseItem item) {
                if (item.isBuildSuccessIndicated()) {
                    future.complete(item);
                } else if (item.isErrorIndicated()) {
                    future.completeExceptionally(new RuntimeException("Error during build: " + item.getErrorDetail()));
                }
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            }
        }));
    }

    @McpTool(name = "docker_inspect_volume",
            description = "Retrieve detailed information about a Docker volume by name.")
    public InspectVolumeResponse inspectVolumeCmd(String name) {
        return internalClient.inspectVolumeCmd(name)
                .exec();
    }

    @McpTool(name = "docker_remove_service",
            description = "Remove a Docker service by its serviceId. returns true if successful.")
    public boolean removeServiceCmd(String serviceId) {
        internalClient.removeServiceCmd(serviceId)
                .exec();
        return true;
    }

    @McpTool(name = "docker_list_containers",
            description = "List Docker containers. if showAll param is true, shows all the contains, if false " +
                    "shows just running). filter can be passed as key value pairs to filter the output and it can " +
                    "be more than one pair in a request but usually it is not required by default.")
    public List<Container> listContainersCmd(boolean showAll, Map<String, String> filter) {
        ListContainersCmd cmd = internalClient.listContainersCmd();
        cmd.withShowAll(showAll);
        if (filter != null) {
            filter.entrySet().stream()
                    .filter(entry -> entry.getKey() != null && !entry.getKey().isBlank())
                    .filter(entry -> entry.getValue() != null && !entry.getValue().isBlank())
                    .forEach(entry ->
                            cmd.withFilter(entry.getKey(), Collections.singletonList(entry.getValue())));

        }

        return cmd.exec();
    }

    @McpTool(
            name = "docker_inspect_swarm",
            description = "Inspect the current Docker Swarm details")
    public Swarm inspectSwarmCmd() {
        return internalClient.inspectSwarmCmd().exec();
    }

    @McpTool(
            name = "docker_push_image",
            description = "Push an image to a registry. registryAddress is the url of registry " +
                    "e.g.: registry.hub.docker.com! pushedImageTag is the name an tag of the image to push!" +
                    "if and only authentication is required, username and password is the credentials to authenticate" +
                    "to the registry, otherwise can be null."
    )
    public PushResponseItem pushImageCmd(@NonNull String registryAddress, @NonNull String pushedImageTag,
                                         @Nullable String username, @Nullable String password)
            throws ExecutionException, InterruptedException, TimeoutException {
        Repository repository = new Repository(registryAddress);
        Identifier identifier = new Identifier(repository, pushedImageTag);

        PushImageCmd cmd = internalClient.pushImageCmd(identifier);
        if (username != null && password != null) {
            AuthConfig authConfig = new AuthConfig();
            authConfig.withUsername(username);
            authConfig.withPassword(password);
            cmd.withAuthConfig(authConfig);
        }

        return Execute.getResult(cmd, (step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT && t instanceof PushResponseItem item) {
                if (item.isErrorIndicated()) {
                    future.completeExceptionally(
                            new McpMediatorException(Objects.requireNonNullElse(item.getErrorDetail(),
                                    "Error happened during the push process").toString()));
                } else {
                    future.complete(item);
                }
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            }
        });
    }


    @McpTool(
            name = "docker_copy_archive_to_container",
            description = """
                      Copies a tar archive from the host filesystem into a running container at the specified\s
                      destination path.
                      This can be used to transfer files or directories into the container.
                      All paths must be absolute, and the archive must be in TAR format.
                      Returns true if successful.
                    
                       Parameters:
                     - containerId: (String, required) The ID or name of the container where the archive will be copied.Must not be null or empty.
                     - archiveFileAbsolutePath: (String, required) Absolute path to the tar archive file on the host system. The file must exist and be in TAR format.
                     - containerDestinationPath: (String, required) Destination absolute path inside the container's filesystem where the archive will be extracted.
                    """)
    public boolean copyArchiveToContainerCmd(@NonNull String containerId, @NonNull String archiveFileAbsolutePath,
                                             @NonNull String containerDestinationPath) throws FileNotFoundException {
        CopyArchiveToContainerCmd cmd = internalClient.copyArchiveToContainerCmd(containerId);
        FileInputStream inputStream = new FileInputStream(new File(archiveFileAbsolutePath));
        cmd.withTarInputStream(inputStream);
        cmd.withRemotePath(containerDestinationPath);
        cmd.exec();
        return true;
    }

    @McpTool(
            name = "docker_stats_container",
            description = """
                    Retrieves container statistics, including CPU, memory, network, and disk usage.\s
                    This method streams real-time statistics for a container and optionally allows for a one-time snapshot (via the noStream parameter).
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container for which the statistics will be fetched.
                      Must not be null or empty. The container should be running.
                    - noStream: (boolean, required) Whether to disable streaming statistics.\s
                      If true, only the current stats will be retrieved without continuous updates.
                      If false, the stats will be streamed until the connection is closed.
                    """)
    public Statistics statsCmd(@NonNull String containerId, boolean noStream)
            throws ExecutionException, InterruptedException, TimeoutException {
        StatsCmd cmd = internalClient.statsCmd(containerId)
                .withNoStream(noStream);

        return Execute.getResult(cmd, ((step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT) {
                future.complete((Statistics) t);
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            }
        }));
    }

    @McpTool(
            name = "docker_disconnect_container_from_network",
            description = """
                    Disconnects a container from a specified Docker network.
                    If the container is running, it will be immediately disconnected from the network, potentially disrupting any network-dependent functionality.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to disconnect from the network.
                      Must not be null or empty. The container must be part of the specified network.
                    - networkId: (String, required) The ID or name of the Docker network to disconnect the container from.
                      Must not be null or empty.
                    - force: (boolean, required) Whether to force the disconnection of the container from the network even if there are active connections.
                      If true, the disconnection will proceed regardless of the container's network state.
                    """)
    public boolean disconnectFromNetworkCmd(@NonNull String containerId, @NonNull String networkId, boolean force) {
        internalClient.disconnectFromNetworkCmd()
                .withContainerId(containerId)
                .withNetworkId(networkId)
                .withForce(force)
                .exec();
        return true;
    }


    @McpTool(
            name = "docker_remove_container",
            description = """
                    Removes a container from Docker. If the container is running, you can optionally force it to stop and remove it.
                    This command will remove the container and its associated data, including filesystem changes,\s
                    unless persistent volumes are used.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to remove. Must not be null or empty.
                      The container must exist in Docker and should either be stopped or forcibly removed if running.
                    - force: (boolean, required) Whether to force the removal of a running container.
                      If true, the container will be stopped and removed, even if it is currently running. If false, the container\s
                      must be stopped manually before removal.
                    """)
    public boolean removeContainerCmd(@NonNull String containerId, boolean force) {
        internalClient.removeContainerCmd(containerId)
                .withForce(force)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_inspect_service",
            description = """
                    Inspects a Docker service and retrieves detailed information about its configuration and state.
                    
                    Parameters:
                    - serviceId: (String, required) The ID or name of the service to inspect. Must not be null or empty.
                      The service must exist in the Docker swarm.
                    
                    Returns:
                    - A Service object containing information about the service.
                    """
    )
    public Service inspectServiceCmd(String serviceId) {
        return internalClient.inspectServiceCmd(serviceId)
                .exec();
    }

    @McpTool(
            name = "docker_remove_secret",
            description = """
                    Removes a secret from Docker.
                    
                    Parameters:
                    - secretId: (String, required) The ID or name of the secret to remove. Must not be null or empty.
                      The secret must exist in Docker. Removing a secret is irreversible and may impact services
                      that depend on it.
                    
                    Returns:
                    - A true if executed successfully.
                    """
    )
    public boolean removeSecretCmd(String secretId) {
        internalClient.removeSecretCmd(secretId).exec();
        return true;
    }

    @McpTool(
            name = "docker_pull_image",
            description = """
                    Pulls a Docker image from a registry.
                    
                    Parameters:
                    - repository: (String, required) The name of the image repository (e.g., "library/nginx").
                      Must not be null or empty.
                    - nameAndTag: (String, required) The tag of the image (e.g., "latest" or "1.21").
                      Typically combined with the repository to form the full image reference.
                    - authConfig: (AuthConfig, optional) The authentication configuration used for private registries.
                      If null, the command assumes no authentication is needed.
                    - platform: (String, optional) The target platform (e.g., "linux/amd64"). If null, the default platform is used.
                    
                    Returns:
                    - (Boolean) True if the image pull is successful; otherwise, an exception is thrown.
                      Errors during the pull process are captured and reported with detailed messages.
                    """
    )
    public Boolean pullImageCmd(String repository, String nameAndTag, AuthConfig authConfig, String platform) throws
            ExecutionException, InterruptedException, TimeoutException {

        PullImageCmd cmd = internalClient.pullImageCmd(repository)
                .withTag(nameAndTag);

        if (authConfig != null) {
            cmd.withAuthConfig(authConfig);
        }

        if (platform != null) {
            cmd.withPlatform(nameAndTag);
        }

        return Execute.getResult(cmd, PullResponseItem::isPullSuccessIndicated, (step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT && t instanceof PullResponseItem item) {
                if (item.isErrorIndicated()) {
                    future.completeExceptionally(
                            new McpMediatorException(Objects.requireNonNullElse(item.getErrorDetail(),
                                    "Error happened during the pull process").toString()));
                } else {
                    future.complete(item);
                }
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            }
        });
    }

    @McpTool(
            name = "docker_inspect_container",
            description = """
                    Inspects a Docker container and retrieves detailed information about its configuration and runtime state.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to inspect. Must not be null or empty.
                      The container must exist in Docker.
                    - showSize: (boolean, required) Whether to include size information in the inspection result.
                      If true, the response will include the container's size on disk.
                    
                    Returns:
                    - (InspectContainerResponse) An object containing comprehensive details about the specified container,
                      including its configuration, state, and optionally its size.
                    """
    )
    public InspectContainerResponse inspectContainerCmd(String containerId, boolean showSize) {
        return internalClient.inspectContainerCmd(containerId)
                .withSize(showSize)
                .exec();
    }

    @McpTool(
            name = "docker_unpause_container",
            description = """
                    Unpauses a previously paused Docker container, allowing its processes to resume execution.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to unpause. Must not be null or empty.
                      The container must be in a paused state; otherwise, this command has no effect.
                    
                    Returns:
                    - (boolean) True if the command is executed successfully. An exception will be thrown if the operation fails.
                    """
    )
    public boolean unpauseContainerCmd(String containerId) {
        internalClient.unpauseContainerCmd(containerId)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_list_images",
            description = """
                    Lists Docker images available on the host, with optional filtering and visibility control.
                    
                    Parameters:
                    - showAll: (boolean, required) Whether to show all images, including intermediate layers.
                      If false, only top-level images are returned.
                    - filter: (Map<String, List<String>>, optional) Filters to apply when listing images.
                      Each key is a filter type (e.g., "reference", "dangling") and the value is a list of filter values.
                      Filters must have non-blank keys and non-empty value lists to be applied.
                    
                    Returns:
                    - (List<Image>) A list of images matching the specified criteria.
                      Each entry contains metadata such as repository, tag, size, and creation time.
                    """
    )
    public List<Image> listImagesCmd(boolean showAll, Map<String, List<String>> filter) {
        ListImagesCmd cmd = internalClient.listImagesCmd()
                .withShowAll(showAll);
        if (filter != null) {
            filter.entrySet().stream()
                    .filter(entry -> entry.getKey() != null && !entry.getKey().isBlank())
                    .filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
                    .forEach(entry ->
                            cmd.withFilter(entry.getKey(), entry.getValue()));
        }

        return cmd.exec();
    }

    @McpTool(
            name = "docker_list_services",
            description = """
                    Lists all Docker services in the current swarm.
                    
                    Parameters:
                    - None.
                    
                    Returns:
                    - (List<Service>) A list of services currently running in the Docker swarm.
                      Each service includes metadata such as ID, name, mode, and task specifications.
                    """
    )
    public List<Service> listServicesCmd() {
        return internalClient.listServicesCmd()
                .exec();
    }

    @McpTool(
            name = "docker_remove_image",
            description = """
                    Removes a Docker image from the local system.
                    
                    Parameters:
                    - imageId: (String, required) The ID or name of the image to remove. Must not be null or empty.
                      The image must exist locally.
                    - force: (boolean, required) Whether to force removal of the image even if it's being used by stopped containers or has multiple tags.
                      If true, the image will be forcibly deleted.
                    - noPrune: (boolean, required) If true, parent images that are no longer used will not be removed.
                      If false, unused parent layers may be deleted along with the image.
                    
                    Returns:
                    - (boolean) True if the image was successfully removed. An exception is thrown on failure.
                    """
    )
    public boolean removeImageCmd(String imageId, boolean force, boolean noPrune) {
        internalClient.removeImageCmd(imageId)
                .withForce(force)
                .withNoPrune(noPrune)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_create_network",
            description = """
                    Creates a Docker network with the specified configuration.
                    
                    Parameters:
                    - name: (String, required) The name of the network to create. Must not be null or empty.
                    - driver: (String, optional) The network driver to use (e.g., "bridge", "overlay"). Defaults to "bridge" if null.
                    - attachable: (boolean, optional) Whether containers can be manually attached to this network.
                    - internal: (boolean, optional) If true, the network is isolated from external networks.
                    - labels: (Map<String, String>, optional) Labels to assign to the network as metadata.
                    
                    Returns:
                    - (CreateNetworkResponse) The response containing details of the created network, including its ID.
                    """
    )
    public CreateNetworkResponse createNetworkCmd(
            @NonNull String name,
            String driver,
            boolean attachable,
            boolean internal,
            Map<String, String> labels
    ) {
        try (CreateNetworkCmd cmd = internalClient.createNetworkCmd()
                .withName(name)
                .withDriver(driver != null ? driver : "bridge")
                .withAttachable(attachable)
                .withInternal(internal)) {
            if (labels != null && !labels.isEmpty()) {
                cmd.withLabels(labels);
            }

            return cmd.exec();
        }
    }

    @McpTool(
            name = "docker_tag_image",
            description = """
                    Tags a Docker image with a new repository and tag name.
                    
                    Parameters:
                    - imageId: (String, required) The ID or current name of the image to tag. Must not be null or empty.
                      The image must exist locally.
                    - imageNameWithRepository: (String, required) The target repository name to assign to the image.
                      For example, "myregistry/myimage".
                    - tag: (String, required) The tag to assign to the image (e.g., "latest", "v1.0").
                    
                    Returns:
                    - (boolean) True if the image was successfully tagged. An exception is thrown on failure.
                    """
    )
    public boolean tagImageCmd(String imageId, String imageNameWithRepository, String tag) {
        internalClient.tagImageCmd(imageId, imageNameWithRepository, tag)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_authenticate",
            description = """
                    Authenticates with a Docker registry using the provided credentials.
                    
                    Parameters:
                    - authConfig: (AuthConfig, required) The authentication configuration containing registry credentials.
                      Must include the username, password, and registry address (e.g., index.docker.io).
                    
                    Returns:
                    - (AuthResponse) A response indicating whether authentication was successful, including a status message
                      and any additional details returned by the registry.
                    """
    )
    public AuthResponse authCmd(AuthConfig authConfig) {
        return internalClient.authCmd()
                .withAuthConfig(authConfig)
                .exec();
    }

    @McpTool(
            name = "docker_exec_command",
            description = """
                    Executes a command inside a running Docker container.
                    
                    This method creates an exec instance in the container and starts it, capturing the output.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to run the command in.
                      Must refer to a running container.
                    - commands: (List<String>, required) The command and its arguments to run, e.g., ["ls", "-la"].
                    - envs: (List<String>, optional) A list of environment variables in the form VAR=value.
                      These will override or supplement the container's environment for the exec session.
                    - workingDir: (String, optional) The working directory inside the container where the command should be run.
                      If null or blank, the container's default is used.
                    
                    Returns:
                    - (String) The output of the executed command as a single string.
                    
                    Throws:
                    - ExecutionException, InterruptedException, TimeoutException if the command fails or times out.
                    """
    )
    public String execCreateCmd(String containerId, List<String> commands, List<String> envs, String workingDir)
            throws ExecutionException, InterruptedException, TimeoutException {
        ExecCreateCmd cmd = internalClient.execCreateCmd(containerId)
                .withCmd(commands.toArray(new String[0]));
        if (envs != null && !envs.isEmpty()) {
            cmd.withEnv(envs);
        }
        if (workingDir != null && !workingDir.isBlank()) {
            cmd.withWorkingDir(workingDir);
        }
        ExecCreateCmdResponse response = cmd.exec();

        ExecStartCmd startCmd = internalClient.execStartCmd(response.getId());

        StringBuilder sb = new StringBuilder();

        Execute.getResult(startCmd, ((step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT && t instanceof Frame frame) {
                sb.append(frame);
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            } else {
                future.complete(null);
            }
        }));

        return sb.toString();
    }

    @McpTool(
            name = "docker_remove_swarm_node",
            description = """
                    Removes a node from the Docker Swarm cluster.
                    
                    Parameters:
                    - swarmNodeId: (String, required) The ID of the node to be removed. Must not be null or empty.
                      The node must exist in the swarm.
                    - force: (boolean, required) Whether to force removal. If true, the node will be removed even if it is still active.
                      If false, the node should be demoted and drained before removal.
                    
                    Returns:
                    - (boolean) True if the operation succeeds. Exceptions will be thrown for failure cases.
                    """
    )
    public boolean removeSwarmNodeCmd(String swarmNodeId, boolean force) {
        internalClient.removeSwarmNodeCmd(swarmNodeId)
                .withForce(force)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_search_images",
            description = """
                    Searches Docker Hub for images matching the specified term.
                    
                    Parameters:
                    - term: (String, required) The search term used to query image names and descriptions on Docker Hub.
                      Must not be null or empty.
                    
                    Returns:
                    - (List<SearchItem>) A list of matching Docker images with metadata such as name, description, and official status.
                    """
    )
    public List<SearchItem> searchImagesCmd(String term) {
        return internalClient.searchImagesCmd(term)
                .exec();
    }

    @McpTool(
            name = "docker_list_networks",
            description = """
                    Lists all Docker networks available on the host.
                    
                    Returns:
                    - (List<Network>) A list of network objects, each containing details such as ID, name, scope, driver,
                      connected containers, and configuration.
                    """
    )
    public List<Network> listNetworksCmd() {
        return internalClient.listNetworksCmd()
                .exec();
    }

    @McpTool(
            name = "docker_remove_volume",
            description = """
                Removes a Docker volume.

                This command deletes a volume from Docker. The volume must not be in use by any containers.
                If the volume is in use, the command will fail.

                Parameters:
                - name: (String, required) The name of the volume to remove.

                Returns:
                - (boolean) Returns true after successfully removing the volume.
                """
    )
    public boolean removeVolumeCmd(String name) {
        internalClient.removeVolumeCmd(name)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_create_container",
            description = """
                Creates a new Docker container with specified configurations.

                This command allows you to configure and create a new container based on a specified image.
                You can set container name, environment variables, working directory, port bindings, volumes, network, and aliases.

                Parameters:
                - image: (String, required) The image to use for creating the container.
                - name: (String, optional) The name of the container. If not provided, Docker will generate one.
                - env: (List<String>, optional) A list of environment variables in the form of key-value pairs.
                - workingDir: (String, optional) The working directory inside the container.
                - portBindings: (List<String>, optional) A list of port bindings. can be in 4 formats: '127.0.0.1:80:8080/tcp' or '80:8080' or '127.0.0.1::8080' or '8080'
                - volumes: (List<String>, optional) A list of volumes to mount into the container.
                - alias: (List<String>, optional) A list of aliases for the container within the Docker network.
                - network: (String, optional) The network to attach the container to.

                Returns:
                - (CreateContainerCmd) The command object to execute for creating the container.
                """
    )
    @SuppressWarnings("java:S107")
    public CreateContainerCmd createContainerCmd(
            String image,
            String name,
            List<String> env,
            String workingDir,
            List<String> portBindings,
            List<String> volumes,
            List<String> alias,
            String network) {

        CreateContainerCmd cmd = internalClient.createContainerCmd(image);


        if (name != null && !name.isBlank()) {
            cmd.withName(name);
        }

        // Set environment variables if provided
        if (env != null && !env.isEmpty()) {
            cmd.withEnv(env);
        }

        // Set the working directory if provided
        if (workingDir != null && !workingDir.isBlank()) {
            cmd.withWorkingDir(workingDir);
        }

        // Set port bindings if provided
        if (portBindings != null && !portBindings.isEmpty()) {
            Objects.requireNonNull(cmd.getHostConfig()).withPortBindings(portBindings.stream().map(PortBinding::parse).toList());
        }

        // Set volumes if provided
        if (volumes != null && !volumes.isEmpty()) {
            cmd.withVolumes(volumes.stream().map(Volume::new).toArray(Volume[]::new));
        }

        // Set aliases if provided
        if (alias != null && !alias.isEmpty()) {
            cmd.withAliases(alias.toArray(new String[0]));
        }

        // Set network if provided
        if (network != null && !network.isBlank()) {
            Objects.requireNonNull(cmd.getHostConfig()).withNetworkMode(network);
        }

        return cmd;
    }

    @McpTool(
            name = "docker_load_image",
            description = """
        Loads a Docker image from a provided tar file absolute address.

        This command allows you to import a Docker image that has been saved using `docker save`, or any compatible image tarball,
        directly into the Docker engine. This is useful for scenarios where the image is obtained from a file, network, or remote source
        and not directly from a registry.

        Parameters:
        - tarFile: (String, required) The input tar file absolute address.

        Returns:
        - true if successfully loaded the image
        """
    )
    public boolean loadImageCmd(String tarFile) throws FileNotFoundException {
        internalClient.loadImageCmd(new FileInputStream(tarFile))
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_list_tasks",
            description = """
        Lists the tasks in a Docker Swarm environment.

        This command retrieves all the tasks currently known to the Docker engine. Tasks are the individual units of work
        in a Swarm service, each representing a running container instance. This is especially useful for monitoring,
        debugging, or inspecting the current state of services in a Swarm cluster.

        Parameters:
        - None

        Returns:
        - (List<Task>) A list of Task objects representing running or completed tasks within the Swarm environment.
        """
    )
    public List<Task> listTasksCmd() {
        return internalClient.listTasksCmd()
                .exec();
    }

    @McpTool(
            name = "docker_save_image",
            description = """
        Saves a Docker image to a local tar file.

        This command exports a specified Docker image (with an optional tag) to a tar archive on the local filesystem.
        This is useful for backing up images, transferring them between systems, or archiving versions for reproducibility.

        Parameters:
        - imageName: (String, required) The name of the Docker image to save.
        - imageTag: (String, required) The tag of the image to save (e.g., "latest").
        - tarFileAbsolutePath: (String, required) The full absolute path where the resulting tar file should be saved.

        Returns:
        - (boolean) true if the image is successfully saved to the specified file.
        
        Throws:
        - IOException if writing to the target file fails or if the image stream cannot be read.
        """
    )
    public boolean saveImagesCmd(String imageName, String imageTag, String tarFileAbsolutePath) throws IOException {
        InputStream stream = internalClient.saveImagesCmd()
                .withImage(imageName, imageTag)
                .exec();

        File targetFile = new File(tarFileAbsolutePath);

        FileUtils.copyInputStreamToFile(stream, targetFile);

        return true;
    }

    @McpTool(
            name = "docker_join_swarm",
            description = """
        Joins the current Docker node to an existing Swarm cluster.

        This command configures the node to join an existing Docker Swarm cluster using the provided advertise address, join token, and remote manager addresses.

        Parameters:
        - advertiseAddr: (String, optional) The address advertised to other nodes in the cluster. This should be reachable by other Swarm nodes.
        - joinToken: (String, optional) The Swarm join token. Required for joining as a worker or manager.
        - remoteAddrs: (List<String>, optional) A list of manager node addresses to connect to during the join operation.

        Returns:
        - (boolean) true if the node successfully joins the Swarm.

        Notes:
        - If `advertiseAddr` or `joinToken` are not provided, Docker's default behavior applies.
        - All `null` entries in `remoteAddrs` are ignored.
        """
    )
    public boolean joinSwarmCmd(String advertiseAddr, String joinToken, List<String> remoteAddrs) {
        JoinSwarmCmd cmd =  internalClient.joinSwarmCmd();
        if (advertiseAddr != null && !advertiseAddr.isBlank()) {
            cmd.withAdvertiseAddr(advertiseAddr);
        }

        if (joinToken != null && !joinToken.isBlank()) {
            cmd.withJoinToken(joinToken);
        }

        if (remoteAddrs != null) {
            cmd.withRemoteAddrs(remoteAddrs.stream().filter(Objects::nonNull).toList());
        }

        cmd.exec();

        return true;
    }

    @McpTool(
            name = "docker_create_volume",
            description = """
        Creates a new Docker volume with optional configurations.

        This command creates a volume that can later be mounted into containers. You can configure the volume's name, driver, driver options, and labels.

        Parameters:
        - name: (String, optional) The name of the volume. If not specified, Docker will generate a unique name.
        - driver: (String, optional) Volume driver to use. Defaults to 'local'.
        - driverOpts: (Map<String, String>, optional) A map of driver-specific options passed during volume creation.
        - labels: (Map<String, String>, optional) Labels to set on the volume for identification and management.

        Returns:
        - (CreateVolumeResponse) The response containing details about the created volume.
        """
    )
    public CreateVolumeResponse createVolumeCmd(
            String name,
            String driver,
            Map<String, String> driverOpts,
            Map<String, String> labels
    ) {
        try(CreateVolumeCmd cmd = internalClient.createVolumeCmd()) {

        if (name != null && !name.isBlank()) {
            cmd.withName(name);
        }

        if (driver != null && !driver.isBlank()) {
            cmd.withDriver(driver);
        }

        if (driverOpts != null && !driverOpts.isEmpty()) {
            cmd.withDriverOpts(driverOpts);
        }

        if (labels != null && !labels.isEmpty()) {
            cmd.withLabels(labels);
        }

        return cmd.exec();
        }
    }

    @McpTool(
            name = "docker_initialize_swarm",
            description = """
        Initializes a new Docker Swarm cluster with the given configuration.

        This command sets up the current Docker Engine as a manager node in a new Swarm cluster using the provided Swarm specification.

        Parameters:
        - swarmSpec: (SwarmSpec, required) Specification for initializing the swarm. It includes information like the orchestration, dispatcher, CA configuration, encryption, and Raft configuration.
        - forceNewCluster: (boolean, required) If true, forces the creation of a new cluster even if one already exists.

        Returns:
        - (boolean) True if the initialization was successful.
        """
    )
    public boolean initializeSwarmCmd(SwarmSpec swarmSpec, boolean forceNewCluster) {
        internalClient.initializeSwarmCmd(swarmSpec)
                .withForceNewCluster(forceNewCluster)
                .exec();
        return true;
    }

    public UpdateServiceCmd updateServiceCmd(String serviceId, ServiceSpec serviceSpec) {
        return internalClient.updateServiceCmd(serviceId, serviceSpec);
    }

    public ListConfigsCmd listConfigsCmd() {
        return internalClient.listConfigsCmd();
    }

    public InspectImageCmd inspectImageCmd(String imageId) {
        return internalClient.inspectImageCmd(imageId);
    }

    public EventsCmd eventsCmd() {
        return internalClient.eventsCmd();
    }

    public ConnectToNetworkCmd connectToNetworkCmd() {
        return internalClient.connectToNetworkCmd();
    }

    public RestartContainerCmd restartContainerCmd(String containerId) {
        return internalClient.restartContainerCmd(containerId);
    }

    public CreateServiceCmd createServiceCmd(ServiceSpec serviceSpec) {
        return internalClient.createServiceCmd(serviceSpec);
    }

    @McpTool(
            name = "docker_remove_network",
            description = """
                Removes a Docker network.

                This command removes a specified Docker network. The network must not be in use by any containers
                at the time of removal. If containers are using the network, they must be disconnected before the
                network can be removed.

                Parameters:
                - networkId: (String, required) The ID or name of the Docker network to be removed. The network must
                            exist in Docker and must not be in use by any containers.

                Returns:
                - (boolean) A boolean indicating the success of the network removal.
                """
    )
    public boolean removeNetworkCmd(String networkId) {
        internalClient.removeNetworkCmd(networkId)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_copy_archive_from_container",
            description = """
                Copies an archive (tarball) from a Docker container to the host filesystem.

                This command allows you to copy files or directories from a container to a specified
                location on the host machine. The copied content is in the form of an archive.

                Parameters:
                - containerId: (String, required) The ID or name of the container from which to copy the archive.
                  The container must exist.
                - containerArchiveFile: (String, required) The path of the file or directory inside the container to copy.
                - hostPath: (String, required) The path on the host where the archive will be saved.

                Returns:
                - (boolean) Returns true after successfully copying the archive from the container to the host.
                """
    )
    public boolean copyArchiveFromContainerCmd(String containerId, String containerArchiveFile, String hostPath) throws IOException {
         InputStream stream = internalClient.copyArchiveFromContainerCmd(containerId, containerArchiveFile)
                .withHostPath(hostPath)
                .exec();
         stream.close();
         return true;
    }

    @McpTool(
            name = "docker_rename_container",
            description = """
                Renames an existing Docker container.

                This command allows you to rename a container while it is stopped or running.
                The new name must not conflict with existing containers.

                Parameters:
                - containerId: (String, required) The ID or name of the container to rename.
                  The container must exist in Docker.
                - newName: (String, required) The new name to assign to the container.
                  The new name must be unique and not empty.

                Returns:
                - (boolean) Returns true after successfully renaming the container.
                """
    )
    public boolean renameContainerCmd(String containerId, String newName) {
        internalClient.renameContainerCmd(containerId)
                .withName(newName)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_pause_container",
            description = """
                    Pauses a running Docker container.
                    
                    This command suspends the container, effectively freezing its state without stopping it.
                    The container can later be resumed using the unpause command.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to pause.
                      The container must be running.
                    
                    Returns:
                    - (boolean) Returns true after successfully pausing the container.
                    """
    )
    public boolean pauseContainerCmd(String containerId) {
        internalClient.pauseContainerCmd(containerId)
                .exec();
        return true;
    }

    @McpTool(
            name = "docker_version",
            description = """
                    Retrieves version information about the Docker server and client.
                    
                    This command fetches details such as the Docker version, API version, and the Go version
                    used to build the client and server.
                    
                    Returns:
                    - (Version) A Version object containing details about the Docker version and related information.
                    """
    )
    public Version versionCmd() {
        return internalClient.versionCmd()
                .exec();
    }


    @McpTool(
            name = "docker_list_swarm_nodes",
            description = """
                    Lists all nodes in the Docker Swarm.
                    
                    This command returns information about all nodes that are part of the current Swarm cluster.
                    
                    Returns:
                    - (List<SwarmNode>) A list of SwarmNode objects representing each node in the cluster.
                      Includes metadata such as ID, hostname, status, role, and availability.
                    """
    )
    public List<SwarmNode> listSwarmNodesCmd() {
        return internalClient.listSwarmNodesCmd()
                .exec();
    }

    @McpTool(
            name = "docker_log_container",
            description = """
                    Retrieves logs from a Docker container.
                    
                    This method fetches stdout and stderr logs from the specified container.
                    You can optionally limit the number of trailing log lines using the 'tail' parameter.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container whose logs you want to retrieve.
                      The container must exist.
                    - tail: (Integer, optional) The number of lines from the end of the logs to include. If null, all logs are returned.
                    
                    Returns:
                    - (String) The concatenated logs as plain text.
                    
                    Throws:
                    - ExecutionException, InterruptedException, TimeoutException if log retrieval fails or times out.
                    """
    )
    public String logContainerCmd(String containerId, Integer tail) throws ExecutionException,
            InterruptedException, TimeoutException {
        LogContainerCmd cmd = internalClient.logContainerCmd(containerId);
        if (tail != null) {
            cmd.withTail(tail);
        } else {
            cmd.withTailAll();
        }

        StringBuilder sb = new StringBuilder();

        Execute.getResult(cmd, ((step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT && t instanceof Frame frame) {
                sb.append(frame);
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            } else {
                future.complete(null);
            }
        }));

        return sb.toString();
    }

    @McpTool(
            name = "docker_prune",
            description = """
                    Removes unused Docker objects to free up system resources.
                    
                    Parameters:
                    - pruneType: (PruneType, required) The type of resources to prune. Must not be null.
                      Valid values typically include: CONTAINERS, VOLUMES, NETWORKS, IMAGES, etc.
                      Each type will remove only the unused or dangling objects of that category.
                    
                    Returns:
                    - (PruneResponse) An object detailing what was removed and how much disk space was reclaimed.
                    """,
            annotations = @McpTool.McpAnnotation(
                    destructiveHint = true
            )
    )
    public PruneResponse pruneCmd(PruneType pruneType) {
        return internalClient.pruneCmd(pruneType)
                .exec();
    }

    @McpTool(
            name = "docker_inspect_network",
            description = """
                    Retrieves detailed information about a Docker network.
                    
                    Parameters:
                    - networkId: (String, required) The ID or name of the network to inspect.
                      Must not be null or empty. The network must exist in the Docker engine.
                    
                    Returns:
                    - (Network) An object containing detailed information about the specified network,
                      including its name, driver, scope, connected containers, IPAM settings, and options.
                    """
    )
    public Network inspectNetworkCmd(String networkId) {
        return internalClient.inspectNetworkCmd()
                .withNetworkId(networkId)
                .exec();
    }

    @McpTool(
            name = "docker_kill_container",
            description = """
                    Sends a signal to a running Docker container to forcibly stop it.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to kill.
                      Must not be null or empty. The container must be running.
                    - signal: (String, optional) The signal to send to the container (e.g., "SIGKILL", "SIGTERM").
                      If null, the default signal ("SIGKILL") is used by Docker.
                    
                    Returns:
                    - (boolean) True if the signal was successfully sent and the container is being stopped.
                      An exception is thrown on failure.
                    """
    )
    public boolean killContainerCmd(String containerId, String signal) {
        KillContainerCmd cmd = internalClient.killContainerCmd(containerId);
        if (signal != null) {
            cmd.withSignal(signal);
        }
        cmd.exec();
        return true;
    }

    @McpTool(
            name = "docker_top_container",
            description = """
                    Retrieves the running processes of a Docker container.
                    
                    Parameters:
                    - containerId: (String, required) The ID or name of the container to retrieve the process information for.
                      Must not be null or empty. The container must be running.
                    
                    Returns:
                    - (TopContainerResponse) A response containing the list of processes running within the container,
                      including details like process ID (PID), user, CPU usage, memory usage, and the command that initiated the process.
                    """
    )
    public TopContainerResponse topContainerCmd(String containerId) {
        return internalClient.topContainerCmd(containerId)
                .exec();
    }

    @McpTool(
            name = "docker_list_volumes",
            description = """
                    Lists Docker volumes with optional filtering.
                    
                    Parameters:
                    - filterName: (String, optional) The name of the filter to apply (e.g., "driver", "label").
                      If null or empty, no filter is applied.
                    - filterValue: (List<String>, optional) The values associated with the filter name.
                      If null or empty, no filter is applied.
                    
                    Returns:
                    - (ListVolumesResponse) A response containing the list of volumes matching the specified criteria.
                      Each volume entry includes metadata such as name, driver, and mount point.
                    """
    )
    public ListVolumesResponse listVolumesCmd(String filterName, List<String> filterValue) {
        ListVolumesCmd cmd = internalClient.listVolumesCmd();
        if (filterName != null && !filterName.isBlank()) {
            cmd.withFilter(filterName, filterValue);
        }
        return cmd.exec();
    }

    @McpTool(
            name = "docker_update_swarm_node",
            description = """
                    Updates the specification of a Docker swarm node.
                    
                    Parameters:
                    - swarmNodeId: (String, required) The ID of the node in the Docker swarm to update. Must not be null or empty.
                      The node must exist in the swarm.
                    - swarmNodeSpec: (SwarmNodeSpec, required) The new specification for the node, which includes settings like
                      availability, labels, and more. Must not be null.
                    
                    Returns:
                    - (boolean) True if the node specification was successfully updated. An exception will be thrown on failure.
                    """
    )
    public boolean updateSwarmNodeCmd(String swarmNodeId, SwarmNodeSpec swarmNodeSpec) {
        internalClient.updateSwarmNodeCmd()
                .withSwarmNodeId(swarmNodeId)
                .withSwarmNodeSpec(swarmNodeSpec)
                .exec();

        return true;

    }


    @McpTool(
            name = "docker_info",
            description = """
                    Retrieves system-wide information about the Docker daemon.
                    
                    Parameters:
                    - None.
                    
                    Returns:
                    - (Info) An object containing detailed information about the Docker engine, including:
                      - Number of containers and images
                      - Operating system and kernel details
                      - Storage driver information
                      - Swarm status
                      - Resource availability (CPUs, memory)
                    """
    )
    public Info infoCmd() {
        return internalClient.infoCmd()
                .exec();
    }

    @McpTool(
            name = "docker_log_service",
            description = """
                    Retrieves logs for a Docker service.
                    
                    Parameters:
                    - serviceId: (String, required) The ID or name of the service to fetch logs for. Must not be null or empty.
                      The service must exist in the Docker swarm.
                    - tail: (Integer, optional) The number of lines from the end of the logs to retrieve.
                      If null, the complete logs are returned. If negative, its absolute value is used.
                    
                    Returns:
                    - (String) A string containing the aggregated log output from the service.
                      Each log entry is appended in order. An exception is thrown if the log retrieval fails.
                    """
    )
    public String logServiceCmd(String serviceId, Integer tail) throws ExecutionException, InterruptedException, TimeoutException {
        LogSwarmObjectCmd cmd = internalClient.logServiceCmd(serviceId);
        if (tail != null) {
            cmd.withTail(Math.abs(tail));
        }

        StringBuilder sb = new StringBuilder();

        Execute.getResult(cmd, ((step, t, future) -> {
            if (step == Execute.ExecutionStep.NEXT && t instanceof Frame frame) {
                sb.append(frame);
            } else if (step == Execute.ExecutionStep.ERROR) {
                future.completeExceptionally((Throwable) t);
            } else {
                future.complete(null);
            }
        }));

        return sb.toString();
    }
}
